import parole, pygame
from parole.shader import *
from parole.input import *

import main, sim, sim_items, sim_creatures
from util import *

class LineChooser(Shader):
    """
    A L{Shader} and UI event handler for letting the user scroll through a list
    of text lines and select one, invoking a callback for that line.
    Handles intput through a command map defined by a given command map
    config.
    TODO: move to parole engine.
    """
    def __init__(self, font, lineCallbacks=None, yDist=10,
            flankers=(r'\PaleTurquoise >>', r'\PaleTurquoise <<'), commandCfg=None,
            fore_rgb=(255,255,255), name=None, alpha=255):
        """
        Creates a new LineChooser.
        @param lineCallbacks A list of C{(line,callback)}-tuples, where
        C{line} is a line of text and C{callback} is the associated functor.
        @param yDist The vertical distance to put between each line.
        """
        super(LineChooser, self).__init__(name or "LineChooser", alpha=alpha)
        self.font = font
        self.lineCallbacks = lineCallbacks or []
        self.lineShaders = {}
        self.yDist = yDist
        self.fore_rgb = fore_rgb
        self.flankers = (TextBlockPass(font, fore_rgb, text=flankers[0]),
                         TextBlockPass(font, fore_rgb, text=flankers[1]))
        self.flankers[0].update()
        self.flankers[1].update()
        self.selectedIdx = self.__firstGoodIdx()
        self.cmdMap = parole.input.CommandMap(commandCfg or
                parole.conf.interface.linechooser, self.handleCommand)
        self.arrange()

    def stringifyLineObj(self, obj):
        return str(obj)

    def arrange(self):
        """
        Creates a L{Pass} for each selectable line and positions it.
        """
        self.clearPasses()
        self.lineShaders.clear()
        maxW = 0
        for line,cb in self.lineCallbacks:
            tbLine = TextBlockPass(self.font, self.fore_rgb,
                    text=self.stringifyLineObj(line))
            tbLine.update()
            maxW = max(maxW, tbLine.width)
            self.lineShaders[id(line)] = tbLine
        flankersW = self.flankers[0].width + self.flankers[1].width + 10
        self.size = (maxW + flankersW,
                self.font.get_linesize()*len(self.lineShaders) + \
                self.yDist*(len(self.lineShaders) - 1))
        selfRect = pygame.Rect((0,0), self.size)
        y = 0
        for line, cb in self.lineCallbacks:
            tbLine = self.lineShaders[id(line)]
            lineRect = pygame.Rect((0,0), tbLine.size)
            lineRect.center = selfRect.center
            self.addPass(tbLine, pos=(lineRect.x, y))
            y += self.font.get_linesize() + self.yDist

        if self.selectedIdx >= len(self.lineCallbacks):
            self.selectedIdx = self.__firstGoodIdx()
        self.select(self.selectedIdx)

    def select(self, lineIdx):
        self.selectedIdx = lineIdx
        for f in self.flankers:
            if f in self.passes:
                self.remPass(f)
        y = lineIdx*self.font.get_linesize() + (lineIdx)*self.yDist
        self.addPass(self.flankers[0], (0,y))
        self.addPass(self.flankers[1], (self.width-self.flankers[1].width, y))

        for p in self.parents:
            if isinstance(p, ScrollView):
                viewX, viewY = p.offset
                if y-10 < viewY or y-10 > viewY + 380:
                    offY = y-10
                    if offY < viewY:
                        offY = max(0, offY)
                    else:
                        offY = min(offY, self.height - 392)
                    p.offset = (0,offY)

    def __firstGoodIdx(self):
        for idx in xrange(len(self.lineCallbacks)):
            if self.lineCallbacks[idx][1]:
                return idx
        return -1

    def __lastGoodIdx(self):
        for idx in reversed(xrange(len(self.lineCallbacks))):
            if self.lineCallbacks[idx][1]:
                return idx
        return -1

    def handleCommand(self, cmd):
        """
        UI event handling. 
        """
        if cmd == "line up":
            for idx in reversed(xrange(self.selectedIdx)):
                if idx != self.selectedIdx and self.lineCallbacks[idx][1]:
                    self.select(idx)
                    return
            self.select(self.__lastGoodIdx())

        elif cmd == "line down":
            for idx in xrange(self.selectedIdx, len(self.lineCallbacks)):
                if idx != self.selectedIdx and self.lineCallbacks[idx][1]:
                    self.select(idx)
                    return
            self.select(self.__firstGoodIdx())

        elif cmd == "select":
            line,callback = self.lineCallbacks[self.selectedIdx]
            callback(line)

#========================================

class LineChooserFrame(Frame):
    def __init__(self, font, lineCallbacks=None, yDist=10,
            flankers=(r'\PaleTurquoise >>', r'\PaleTurquoise <<'), commandCfg=None,
            fore_rgb=(255,255,255), name=None, alpha=255, title=None):
        self.chooser = LineChooser(font, lineCallbacks, yDist, flankers,
                commandCfg, fore_rgb, name, alpha)
        self.chooser.update()
        #self.chBG = ColorField((0,0,128,200), (self.chooser.width+20,
        #    self.chooser.height+20))
        if title:
            txt = TextBlockPass(font, fore_rgb, text=title, wrap='word',
                    wrap_width = 300)
            txt.update()
        height = self.chooser.height+20+(title and txt.height+10 or 0)
        self.chBG = ColorField((128,0,0,220), (max(self.chooser.width,
            (title and txt.width or 0))+20+(height > 450 and 10 or 0),
            height))
        self.scroll = ScrollView((self.chBG.width, min(450, self.chBG.height)),
            vbar=VerticalScrollbar((128,128,0), (64,64,0), 10))
        self.scroll.addPass(self.chBG)

        if title:
            txtRect = pygame.Rect((0,0), txt.size)
            txtRect.centerx = self.chBG.rect.centerx
            self.scroll.addPass(txt, pos=(txtRect.x , 10))
            chRect = pygame.Rect((0,0), self.chooser.size)
            chRect.centerx = self.chBG.rect.centerx
            self.scroll.addPass(self.chooser, pos=(chRect.x,10+txtRect.h+10))
        else:
            self.scroll.addPass(self.chooser, pos=(10,10))

        borders = (None,None, 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        super(LineChooserFrame, self).__init__(borders)

        self.addPass(self.scroll, pos=(0,0))

#========================================

def fadeOut(time, rgb=(0,0,0), clearAfter=True):
    startTime = parole.time()
    r, g, b = rgb
    alpha = 0
    fader = ColorField((r,g,b,alpha),
            parole.display.getSurface().get_size())
    parole.display.scene.add(fader, pos=(0,0))
    parole.pushAnimation()
    while parole.time() - startTime < time:
        alpha = ((parole.time() - startTime)/float(time)) * 255.0
        fader.rgb = (r,g,b,int(alpha))
        yield alpha
    if clearAfter:
        parole.display.scene.removeAll()
        parole.display.clearSurface(parole.display.getSurface())
    else:
        parole.display.scene.remove(fader)
    yield
    parole.popAnimation()

#========================================

def messageBox(text, align='center', textWidth=274):
    """
    TODO: move to parole engine
    """
    font = parole.resource.getFont("fonts/Arial.ttf", 14)
    block = parole.shader.TextBlockPass(font, (255,255,255),
            wrap_width=textWidth, bg_rgb=(128,0,0), align=align, wrap='word')
    block.text = text
    block.update()
    sdr = parole.shader.Shader("FrameContents", 
            (block.width+20, block.height+20))
    sdr.addPass(parole.shader.ColorField((128,0,0), sdr.size))
    sdr.addPass(block, (10,10))
    mbox = parole.shader.Frame((parole.shader.VerticalBevel((0,0,0), 
        (128,128,128), (255,255,255),1, 2, 1),
        parole.shader.VerticalBevel((0,0,0), (128,129,128), (255,255,255), 1, 2, 1),
        parole.shader.HorizontalBevel((255,255,255), (128,128,128), (0,0,0), 1,
            2, 1),
        parole.shader.HorizontalBevel((255,255,255), (128,128,128), (0,0,0), 1,
            2, 1),
        None,None,None,None),
        contents=[sdr])
    mbox.update()
    parole.display.scene.add(mbox, pos=mbox.centeredPos())
    return mbox

#========================================

def commandKeys(command, group):
    keys = []
    for k,v in group.iteritems():
        if v == command:
            keys.append(k)
    return keys

#========================================

def init():
    global frameBorders
    frameBorders = (None,None, 
        HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
        HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
        None, None, None, None)

#========================================

class StatUpper(Frame):
    testify = \
"""
  %s
  Choose a stat to increase 
  ({\Gold %d} points left)
 
  [{\Gold %s}]\tStrength: %d
  [{\Gold %s}]\tDexterity: %d
  [{\Gold %s}]\tConstitution: %d
  [{\Gold %s}]\tPerception: %d
 
"""
    def __init__(self, points, heading="You gain a level!"):
        self.points = points
        self.heading = heading
        self.str, self.dex, self.con, self.per = 10, 10, 10, 10
        if main.player:
            self.str = main.player.strength
            self.dex = main.player.dexterity
            self.con = main.player.constitution
            self.per = main.player.perception

        self.txt = TextBlockPass(main.arial, (255,255,255))
        self.updateText()
        self.bg = ColorField((128,0,0,200), self.txt.size)

        super(StatUpper, self).__init__(frameBorders, contents=[self.bg,
            self.txt])
        self.update()

        self.cmdMap = parole.input.CommandMap(parole.conf.interface.statchooser, 
            self.handleCommand)

    def updateText(self):
        self.txt.text = self.testify % \
                (self.heading, self.points, 
                 '{\White /}'.join(commandKeys("strength",
                     parole.conf.interface.statchooser.keypresses)),
                 self.str, 
                 '{\White /}'.join(commandKeys("dexterity",
                     parole.conf.interface.statchooser.keypresses)),
                 self.dex,
                 '{\White /}'.join(commandKeys("constitution",
                     parole.conf.interface.statchooser.keypresses)),
                 self.con, 
                 '{\White /}'.join(commandKeys("perception",
                     parole.conf.interface.statchooser.keypresses)),
                 self.per)
        self.txt.update()

    def push(self):
        parole.display.scene.add(self, pos=self.centeredPos())
        parole.pushUIEventHandler(self.cmdMap)
        self.active = True

    def pop(self):
        parole.display.scene.remove(self)
        parole.popUIEventHandler()
        self.active = False

    def handleCommand(self, cmd):
        if cmd == 'strength':
            self.str += 1
        elif cmd == 'dexterity':
            self.dex += 1
        elif cmd == 'constitution':
            self.con += 1
        elif cmd == 'perception':
            self.per += 1

        self.points -= 1
        if self.points <= 0:
            self.pop()

        self.updateText()

#========================================

class ModalBox(Frame):
    """
    TODO: move to parole engine
    """

    def __init__(self, text, font, onClose, textWidth=500):
        self.txt = parole.shader.TextBlockPass(font, (255,255,255),
                wrap_width=textWidth, wrap='word')
        self.txt.text = text
        self.txt.update()
        #parole.debug('*** text size: %r', self.txt.size)
        self.bg = ColorField((100,0,0,255), (min(textWidth+20, self.txt.width+20),
                                             min(420, self.txt.height+20)))

        borders = (None,None, 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        super(ModalBox, self).__init__(borders, contents=[self.bg])

        self.scroll = None
        if self.txt.height > self.bg.height:
            self.scroll = ScrollView((self.bg.width+10, self.bg.height),
                vbar=VerticalScrollbar((128,128,0), (64,64,0), 10))
            self.scroll.addPass(self.txt)
            self.addPass(self.scroll, pos=(10,10))
        else:
            self.addPass(self.txt, pos=(10,10))
        self.update()
        self.instructions = TextBlockPass(font, (196,196,196))
        self.instructions.text = r"[%s to close]" % \
            r'{\Gold /}'.join(commandKeys("clear",
                parole.conf.interface.mainplay.keypresses))
        self.instructions.update()
        self.addPass(self.instructions, pos=(self.width/2 - \
            self.instructions.width/2, self.height))
        self.update()
        self.bg.size = self.size
        self.update()

        self.onClose = onClose
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)

    def push(self):
        parole.display.scene.add(self, pos=self.centeredPos())
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        parole.display.scene.remove(self)
        parole.popUIEventHandler()

    def handleCommand(self, cmd):
        if cmd == "clear":
            self.pop()
            self.onClose()
        elif cmd == "walk north" and self.scroll:
            self.scroll.scrollPixels(0, -10)
        elif cmd == "walk south" and self.scroll:
            self.scroll.scrollPixels(0, 10)
        elif cmd == "view up" and self.scroll:
            self.scroll.scrollPixels(0, -100)
        elif cmd == "view down" and self.scroll:
            self.scroll.scrollPixels(0, 100)
                    
#========================================

class TextLineInput(object):
    """
    TODO: move to parole engine
    """
    def __init__(self, prompt, font, callback, width=400):
        rlBox = parole.shader.ReadLineBox(font, width, 
            prompt=prompt)
        self.frame = parole.shader.Frame(frameBorders, 
                contents=[parole.shader.ColorField((128,0,0,200),
                    (rlBox.width+10, rlBox.height)), rlBox])
        self.frame.update()
        self.rl = parole.input.ReadLine(self.__doCallback, rlBox.onInput)
        self.__callback = callback

    def push(self):
        parole.display.scene.add(self.frame, pos=self.frame.centeredPos())
        parole.pushUIEventHandler(self.rl)

    def pop(self):
        parole.display.scene.remove(self.frame)
        parole.popUIEventHandler()

    def __doCallback(self, readline):
        self.pop()
        self.__callback(readline.text)

#========================================

class DirectionChooser(object):
    """
    Waits for a direction command. Calls handleFn with displacement of direction
    received. If canceled, pops itself and calls cancelFn if not None.
    Up to user to display a prompt before pushing the chooser.
    """
    def __init__(self, handleFn, cancelFn=None):
        self.handleCancel = (handleFn, cancelFn)
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)

    def push(self):
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        parole.popUIEventHandler()

    def handleCommand(self, cmd):
        if cmd.startswith('walk '):
            dir = cmd.split()[1:][0]
            disp = main.displacement(dir)
            self.pop()
            self.handleCancel[0](disp)
        elif cmd == 'clear':
            self.pop()
            if self.handleCancel[1]:
                self.handleCancel[1]()

#========================================

class ObjectChooser(object):
    def __init__(self, objs, handleFn, cancelFn=None, title=None,
            canCancel=True, choosableType=sim.Obj, popOnChoice=True):
        self.objs = objs
        self.handleCancel = (handleFn, cancelFn)
        self.canCancel = canCancel
        self.popOnChoice = popOnChoice

        choices = []
        for obj in objs:
            if choosableType and isinstance(obj, choosableType):
                choices.append((obj, self.doChoose))
            elif choosableType:
                choices.append((obj, None))
            else:
                choices.append((obj, self.doChoose))

        self.chFrame = LineChooserFrame(main.arial, 
                choices,
                title=title)
        self.chFrame.update()
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand, peek=True)

    def push(self):
        parole.display.scene.add(self.chFrame, pos=self.chFrame.centeredPos())
        parole.pushUIEventHandler((self.cmdMap, self.chFrame.chooser.cmdMap))

    def pop(self):
        parole.display.scene.remove(self.chFrame)
        parole.popUIEventHandler()

    def doChoose(self, obj):
        parole.debug('Chose %s', obj)
        if self.popOnChoice:
            self.pop()
        self.handleCancel[0](obj)

    def handleCommand(self, cmd):
        if cmd == 'clear' and self.canCancel:
            self.pop()
            if self.handleCancel[1]:
                self.handleCancel[1]()

#========================================

class StatBox(Frame, sim.Listener):
    row1Txt = r"{\Gold HP}:%s/%s {\Gold STM}:%s/%s {\Gold SPD}:%s {\Gold EV}:%s {\Gold PV}:%s {\Gold HIT}:%s {\Gold DMG}:%s"
    row2Txt = r"{\Gold STR}:%s {\Gold DEX}:%s {\Gold CON}:%s {\Gold PER}:%s {\Gold XP}:%s/%s {\Gold WGT}:%s/%s"
    def __init__(self, font, creature):
        borders = (
                VerticalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                VerticalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None,
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        Frame.__init__(self, borders)

        self.font = font
        self.creature = creature
        self.row1 = TextBlockPass(font, (255,255,255))
        self.row2 = TextBlockPass(font, (255,255,255))
        self.bg = ColorField((164,0,0,128), (10,10))
        self.addPass(self.bg)
        self.addPass(self.row1, pos=(5,5))
        self.addPass(self.row2, pos=(5,5+font.get_linesize()+2))

    def touch(self, *args, **kwargs):
        Frame.touch(self, *args, **kwargs)
        parole.debug('Touched StatBox.')

    def colorizedStat(self, stat):
        tot = self.creature[stat]
        base = getattr(self.creature, stat)
        if base < tot:
            return "{\\Green %s}" % tot
        elif base > tot:
            return "{\\Red %s}" % tot
        return "%s" % tot

    def update(self, *args, **kwargs):
        if not self.dirty:
            return
        parole.debug('Updating StatBox')

        c = self.creature
        self.row1.text = self.row1Txt % (c['hp'], c['maxHP'], c['stamina'],
                c['maxStamina'], c['speed'], c['ev'], c['pv'], c['hit'],
                c['damageSkew'])
        self.row1.update()
        self.row2.text = self.row2Txt % (self.colorizedStat('strength'),
                self.colorizedStat('dexterity'),
                self.colorizedStat('constitution'),
                self.colorizedStat('perception'), c.level, c.xp,
                round(c.inventoryWeight(),1), self.colorizedStat('capacity'))
        self.row2.update()
        self.bg.size = (400,#max(self.row1.width, self.row2.width) + 10, 
                self.row1.height + self.row2.height + 12)

        Frame.update(self, *args, **kwargs)

    def listen(self, event):
        if event.id in ('set stat', 'increase stat', 'decrease stat'):
            creature, statname, value = event.args
            if creature is self.creature:
                parole.debug('Touching status box')
                self.touch()
        elif event.id in ('enter inventory', 'leave inventory'):
            item, creature = event.args
            if creature is self.creature:
                parole.debug('Touching status box')
                self.touch()


#========================================

class TileChooser(object):
    def __init__(self, examineText=None, chooseCallback=None,
            cancelCallback=None):
        self.mapFrame = main.mapFrame
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)
        self.examineAnnote = None
        self.examinedTile = None
        self.examineText = examineText
        self.chooseCallback = chooseCallback
        self.cancelCallback = cancelCallback

    def push(self):
        self.examineTile(main.player.parentTile)
        self.mapFrame.selectTile(None)
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        if self.examineAnnote:
            self.mapFrame.removeAnnotation(self.examineAnnote)
            self.examineAnnote = None
            self.mapFrame.selectTile(main.player.pos)
        parole.popUIEventHandler()

    def handleCommand(self, cmd):
        if cmd == 'clear':
            self.pop()
            if self.cancelCallback:
                self.cancelCallback()
            return

        elif cmd.startswith('walk '):
            dir = cmd.split()[1:][0]
            dx, dy = displacement(dir)
            newPos = (self.examineAnnote.tile.col + dx, 
                      self.examineAnnote.tile.row + dy)
            if newPos[0] < 0 or newPos[0] >= self.examineAnnote.tile.map.cols \
                or newPos[1] < 0 or newPos[1] >= self.examineAnnote.tile.map.rows:
                    return
            newTile = self.examineAnnote.tile.map[newPos]
            self.mapFrame.removeAnnotation(self.examineAnnote)
            self.examineTile(newTile)
            return

        elif cmd == "select" and self.chooseCallback is not None:
            self.pop()
            self.chooseCallback(self.examinedTile)
            return

    def examineTile(self, tile):
        examineText = self.examineText or 'Here'
        if self.chooseCallback is not None:
            examineText += '\n{\Gold [%s to select]}' % \
                    (main.commandToKeyMapping['select'],)
        examineText += '\n{\Gold [%s to clear]}' % \
                (main.commandToKeyMapping['clear'],)
        self.examineAnnote = self.mapFrame.annotate(tile, examineText)
        self.examinedTile = tile


#========================================

class ExamineInterface(TileChooser):
    def __init__(self):
        TileChooser.__init__(self)

    def handleCommand(self, cmd):
        if cmd == 'examine':
            # Pressing examine again: detailed examine (ie monster desc)
            for obj in self.examinedTile:
                if obj.description and (isinstance(obj, sim_creatures.Monster) or
                        isinstance(obj, sim_creatures.NPC)):
                    ModalBox(('{\Gold %s:}\n' % obj.noun(capitalized=1)) + \
                            obj.description, 
                            main.arial, 
                            lambda: None).push()
                    break
            return
        else:
            TileChooser.handleCommand(self, cmd)


    def examineTile(self, tile):
        if visible(tile):
            examineText = 'You see: %s.' % ', '.join([x.noun() for x in \
                    main.player.visibleObjectsAt(tile)])
        else:
            examineText = 'You cannot see there.'

        hasMonster = False
        hasNPC = False
        for obj in main.player.visibleObjectsAt(tile):
            if isinstance(obj, sim_creatures.Monster) and obj.description:
                hasMonster = True
                break
            elif isinstance(obj, sim_creatures.NPC) and obj.description:
                hasNPC = True
                break
        if hasMonster and visible(tile):
            examineText += '\n{\Gold [%s for monster description]}' % \
                    (main.commandToKeyMapping['examine'],)
        elif hasNPC and visible(tile):
            examineText += '\n{\Gold [%s for character description]}' % \
                    (main.commandToKeyMapping['examine'],)

        self.examineText = examineText
        TileChooser.examineTile(self, tile)
            
        if main.player.monsterReporting:
            parole.info('Examining tile [%d,%d]', tile.col, tile.row)
            self.reportMonster(tile)

    def reportMonster(self, tile):
        parole.info('\tLight intensity at tile: %f', tile.lightIntensity)
        for obj in tile:
            if isinstance(obj, sim_creatures.Creature):
                parole.info('Reporting on: %r', obj)
                for statname in obj.statNames:
                    if 'Mod' not in statname:
                        total = obj[statname]
                        base = getattr(obj, statname)
                        try:
                            mod = getattr(obj, statname+'Mod')
                        except AttributeError:
                            mod = 'n/a'
                        parole.info('\t%s = %s = %s + %s', statname, total,
                                base, mod)


#========================================

class TargetTileInterface(object):
    def __init__(self, callback, canCancel=True, cancelCallback=None,
            visibleOnly=True, overlayPath=False, maxRange=None):
        self.mapFrame = main.mapFrame
        self.cmdMap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            self.handleCommand)
        self.examineAnnote = None
        self.examinedTile = None
        self.callback = callback
        self.canCancel = canCancel
        self.cancelCallback = cancelCallback
        self.visibleOnly = visibleOnly
        self.overlayPath = overlayPath
        self.pathOverlays = {} # tile -> overlay
        self.maxRange = maxRange

    def handleCommand(self, cmd):
        if cmd == 'examine':
            # Pressing examine again: detailed examine (ie monster desc)
            for obj in self.examinedTile:
                if obj.description and (isinstance(obj, sim_creatures.Monster) or\
                        isinstance(obj, sim_creatures.NPC)) :
                    ModalBox(('{\Gold %s:}\n' % obj.noun(capitalized=1)) + \
                            obj.description, 
                            main.arial, 
                            lambda: None).push()
                    break
            return

        elif cmd == 'clear' and self.canCancel:
            self.pop()
            if self.cancelCallback:
                self.cancelCallback()
            return

        elif cmd.startswith('walk '):
            dir = cmd.split()[1:][0]
            dx, dy = displacement(dir)
            newPos = (self.examineAnnote.tile.col + dx, 
                      self.examineAnnote.tile.row + dy)
            if newPos[0] < 0 or newPos[0] >= self.examineAnnote.tile.map.cols \
                or newPos[1] < 0 or newPos[1] >= self.examineAnnote.tile.map.rows:
                    return
            newTile = self.examineAnnote.tile.map[newPos]
            self.mapFrame.removeAnnotation(self.examineAnnote)
            self.examineTile(newTile)
            return

        elif cmd == "select":
            if (not self.visibleOnly) or visible(self.examinedTile):
                self.pop()
                self.callback(self.examinedTile)
            return

    def examineTile(self, tile):
        if self.overlayPath:
            for otile, overlay in self.pathOverlays.iteritems():
                otile.removeOverlay(overlay)
            self.pathOverlays.clear()
            path = list(parole.map.bresenhamPoints(main.player.pos, (tile.col,
                tile.row)))
            for pos in path:
                if not self.maxRange:
                    overlay = parole.map.ReticleOverlayShader(tile.size)
                else:
                    overlay = parole.map.ReticleOverlayShader(tile.size,
                            parole.display.interpolateRGB(
                                (0,255,0), (255,0,0),
                                min(1., distance(main.player.pos, pos) /\
                                    float(self.maxRange))))
                otile = tile.map[pos]
                otile.addOverlay(overlay)
                self.pathOverlays[otile] = overlay

        if self.maxRange and distance(main.player.pos, (tile.col, tile.row)) >=\
                self.maxRange:
            examineText = "{\Orange Out of Range}\n"
        else:
            examineText = ""

        if visible(tile):
            examineText += 'Target: %s.' % ', '.join([x.noun() for x in \
                    main.player.visibleObjectsAt(tile)])
            examineText += '\n{\Gold [%s to select]}' % \
                    (main.commandToKeyMapping['select'],)
        else:
            examineText += 'You cannot see there.'
            if not self.visibleOnly:
                examineText += '\n{\Gold [%s to select]}' % \
                        (main.commandToKeyMapping['select'],)

        if self.canCancel:
            examineText += '\n{\Gold [%s to cancel]}' % \
                (main.commandToKeyMapping['clear'],)
        hasMonster = False
        for obj in main.player.visibleObjectsAt(tile):
            if isinstance(obj, sim_creatures.Monster) and obj.description:
                hasMonster = True
                break
        if hasMonster and visible(tile):
            examineText += '\n{\Gold [%s for monster description]}' % \
                    (main.commandToKeyMapping['examine'],)
        self.examineAnnote = self.mapFrame.annotate(tile, examineText)
        self.examinedTile = tile
            
        if main.player.monsterReporting:
            self.reportMonster(tile)

    def reportMonster(self, tile):
        for obj in tile:
            if isinstance(obj, sim_creatures.Creature):
                parole.info('Reporting on: %r', obj)
                for statname in obj.statNames:
                    if 'Mod' not in statname:
                        total = obj[statname]
                        base = getattr(obj, statname)
                        try:
                            mod = getattr(obj, statname+'Mod')
                        except AttributeError:
                            mod = 'n/a'
                        parole.info('\t%s = %s = %s + %s', statname, total,
                                base, mod)

    def push(self):
        self.examineTile(main.player.parentTile)
        self.mapFrame.selectTile(None)
        parole.pushUIEventHandler(self.cmdMap)

    def pop(self):
        if self.examineAnnote:
            self.mapFrame.removeAnnotation(self.examineAnnote)
            self.mapFrame.selectTile(main.player.pos)
        if self.overlayPath:
            for otile, overlay in self.pathOverlays.iteritems():
                otile.removeOverlay(overlay)
            self.pathOverlays.clear()
        parole.popUIEventHandler()

#========================================

class StatPercentBar(Shader, sim.Listener):
    
    def __init__(self, creature, stat, maxStat, size, mainColor, lossColor=None,
            gainColor=None, label=None, labelFont=None, labelColor=(255,255,255)):
        Shader.__init__(self, 'StatPercentBar', size)
        self.creature = creature
        self.stat = stat
        self.maxStat = maxStat
        self.mainColor = mainColor
        self.lossColor = lossColor
        self.gainColor = gainColor

        self.percentage = float(self.creature[self.stat]) / \
                          float(self.creature[self.maxStat])
        self.percentage = max(self.percentage, 0)
        self.lastPercentage = self.percentage

        self.mainField = ColorField(self.mainColor,
                (int(self.size[0]*self.percentage), self.size[1]))
        self.lossField = lossColor and ColorField(self.lossColor, (0,0))
        self.gainField = gainColor and ColorField(self.gainColor, (0,0))

        self.addPass(self.mainField)

        if self.lossField:
            self.addPass(self.lossField)
        if self.gainField:
            self.addPass(self.gainField)

        if label:
            self.label = TextBlockPass(labelFont or main.proggySmall,
                    labelColor, text=label)
            self.label.update()
            self.addPass(self.label, pos=self.label.centeredPos(self))
            self.label.pos = (self.label.pos[0], self.label.pos[1]+1)

    def listen(self, event):
        if event.id in ('set stat', 'increase stat', 'decrease stat'): 
            creature, statname, value = event.args
            if creature is self.creature and statname == self.stat:
                parole.debug('Touching StatPercentBar')
                self.touch()
        elif event.id == 'get command':
            parole.debug('Touching StatPercentBar')
            self.touch()

    def update(self, *args, **kwargs):
        if not self.dirty:
            return
        parole.debug('Updating StatPercentBar')

        self.lastPercentage = self.percentage
        self.percentage = float(self.creature[self.stat]) / \
                          float(self.creature[self.maxStat])
        self.percentage = max(self.percentage, 0)
        self.mainField.size = (int(round(self.size[0]*self.percentage)),
                self.size[1])

        if self.percentage == self.lastPercentage:
            if self.lossField:
                self.lossField.size = (0,0)
            if self.gainField:
                self.gainField.size = (0,0)
        elif self.percentage < self.lastPercentage and self.lossField:
            self.lossField.pos = (int(round(self.percentage*self.width)), 0)
            self.lossField.size = \
                    (int(round((self.lastPercentage - self.percentage) * \
                        self.width)), self.height)
        elif self.percentage > self.lastPercentage and self.gainField:
            self.gainField.pos = (int(round(self.lastPercentage*self.width)), 0)
            self.gainField.size = \
                    (int(round((self.percentage - self.lastPercentage) * \
                        self.width)), self.height)

        Shader.update(self, *args, **kwargs)

class StatusBox(Frame, sim.Listener):
    def __init__(self, font, creature, size):
        borders = (
                None,
                None,
                None,
                HorizontalBevel((200,200,0), (128,128,0), (64,64,0), 1, 3, 1), 
                None, None, None, None)
        Frame.__init__(self, borders)

        self.font = font
        self.creature = creature
        self.statusNotes = []
        self.row1 = TextBlockPass(font, (255,255,255))
        self.row2 = TextBlockPass(font, (255,255,255))
        self.bg = ColorField((164,0,0,128), size)
        self.addPass(self.bg)
        self.addPass(self.row1, pos=(5,5))
        self.addPass(self.row2, pos=(5,5+font.get_linesize()+2))

    def update(self, *args, **kwargs):
        if not self.dirty:
            return

        #parole.debug('Updating StatusBox')
        c = self.creature
        self.row1.text = "{\Gold TOLA:} %.1fs {\Gold Light:} %.1f" % \
            (c.timeLastAction/1000., c.parentTile and\
                    c.parentTile.lightIntensity or 0.)
        self.row1.update()
        if self.statusNotes:
            self.row2.text = ' '.join(self.statusNotes)
        else:
            self.row2.text = "Normal"
        self.row2.update()

        Frame.update(self, *args, **kwargs)

    #def listen(self, event):
    #    self.touch()
    #    #if event.id == 'fatigue' and event.args[0] is self.creature:
    #    #    self.touch()

class Option(object):
    def __init__(self, name, displayName):
        self.name = name
        self.displayName = displayName
    def __str__(self):
        return self.displayName

class OptionsMenu(object):
    def __init__(self):
        c = parole.conf
        self.fullscreen = Option('fullscreen', "Fullscreen: %s" %\
                self.displayValue(c.display.fullscreen))
        self.music = Option('music', "Music: %s" %\
                self.displayValue(c.interface.music.enabled))
        self.debug = Option('debug', 'Debug Logging: %s' %\
                self.displayValue(c.general.loglevel=='debug'))
        self.animate = Option('animate', 'Animate Movement: %s' %\
                self.displayAnimateValue(c.interface.animateAllMovement))
        options = [
            "{\\Violet Edit {\White %s} for many more options!}" % main.configFileName,
            self.fullscreen,
            self.music,
            self.debug,
            self.animate,
            '{\\Silver [%s to return]}' % main.commandToKeyMapping['clear']
        ]
        self.chooser = ObjectChooser(options, self.handleOption, canCancel=True,
                choosableType=Option, title="{\Gold Options}", popOnChoice=False)

    def push(self):
        self.chooser.push()

    def pop(self):
        self.chooser.pop()

    def displayValue(self, val):
        if type(val) is bool and val:
            return "{\\Cyan ON}"
        elif type(val) is bool:
            return "{\\Orange OFF}"
        else:
            return val

    def displayAnimateValue(self, val):
        if val:
            return "{\\Cyan ALL}"
        return "{\\Orange Projectiles Only}"

    def handleOption(self, option):
        parole.debug("Chose option: %s", option.name)
        c = parole.conf
        if option.name == 'debug':
            curLev = parole.conf.general.loglevel
            if curLev == 'debug':
                parole.conf.general.loglevel = 'info'
            else:
                parole.conf.general.loglevel = 'debug'
            parole.conf.changed()
            self.debug.displayName = "Debug Logging: %s" %\
                    self.displayValue(parole.conf.general.loglevel=='debug')
        elif option.name == 'fullscreen':
            c.display.fullscreen = not c.display.fullscreen
            self.fullscreen.displayName = "Fullscreen: %s" %\
                    self.displayValue(c.display.fullscreen)
            c.changed()
        elif option.name == 'music':
            c.interface.music.enabled = not c.interface.music.enabled
            self.music.displayName = "Music: %s" %\
                    self.displayValue(c.interface.music.enabled)
            c.changed()
        elif option.name == 'animate':
            c.interface.animateAllMovement = not c.interface.animateAllMovement
            self.animate.displayName = 'Animate Movement: %s' %\
                self.displayAnimateValue(c.interface.animateAllMovement)


        self.chooser.chFrame.chooser.arrange()
        self.chooser.chFrame.update()

#========================================

otherType = str
inventoryViewCategories = [
        (sim_items.Weapon, "{\Cyan Weapons}"),
        (sim_items.LightSource, "{\Cyan Light Sources}"),
        (sim_items.Armor, "{\Cyan Armor}"),
        (sim_items.Potion, "{\Cyan Potions}"),
        (sim_items.Scroll, "{\Cyan Scrolls}"),
        (otherType, "{\Cyan Other Items}"),
]

def inventoryView(inventory):
    """
    For use with ObjectChooser, formats an inventory (a sequence of Item
    subclasses) according to category divisions.
    """
    categorized = dict([(cat, []) for (cat, catText) in inventoryViewCategories])
    for obj in inventory:
        found = 0
        for cat in categorized:
            if isinstance(obj, cat):
                categorized[cat].append(obj)
                found = 1
                break
        if not found:
            categorized[otherType].append(obj)
    view  = []
    for cat, catText in inventoryViewCategories:
        objs = categorized[cat]
        if len(objs):
            view += [catText] + objs 
    return view

